﻿using System;
using System.Threading;

namespace Hsenl {
    public class IdHelper {
        private static IdHelper _instance;
        private static readonly object _lock = new object();

        #region Snowflake

        private const long Twepoch = 1288834974657L; // 起始时间戳 (2010-04-25 00:00:00 GMT)

        private const int WorkerIdBits = 5; // 机器ID所占的位数
        private const int DatacenterIdBits = 5; // 数据中心ID所占的位数
        private const int SequenceBits = 12; // 序列号所占的位数

        private const long MaxWorkerId = -1L ^ (-1L << WorkerIdBits); // 最大机器ID
        private const long MaxDatacenterId = -1L ^ (-1L << DatacenterIdBits); // 最大数据中心ID
        private const long MaxSequence = -1L ^ (-1L << SequenceBits); // 最大序列号

        private readonly long workerId;
        private readonly long datacenterId;
        private long sequence = MaxSequence;

        private long lastTimestamp = -1L;

        public static long GenerateSnowflakeId() {
            lock (_lock) {
                _instance ??= new IdHelper(0, 0);
                var id = _instance.NextId();
                return id;
            }
        }

        // 例如我有三个数据中心, 每个数据中心五个机子, 可以填(0-4, 0-2)
        private IdHelper(long workerId, long datacenterId) {
            if (workerId > MaxWorkerId || workerId < 0) {
                throw new ArgumentException($"Worker ID can't be greater than {MaxWorkerId} or less than 0");
            }

            if (datacenterId > MaxDatacenterId || datacenterId < 0) {
                throw new ArgumentException($"Datacenter ID can't be greater than {MaxDatacenterId} or less than 0");
            }

            this.workerId = workerId;
            this.datacenterId = datacenterId;
        }

        private long NextId() {
            var timestamp = GetTimestamp();
            if (timestamp < this.lastTimestamp) {
                throw new Exception($"Clock moved backwards. Refusing to generate id for {this.lastTimestamp - timestamp} milliseconds");
            }

            if (this.lastTimestamp == timestamp) {
                this.sequence = (this.sequence + 1) & MaxSequence;
                if (this.sequence == 0) {
                    timestamp = WaitNextMillis(this.lastTimestamp);
                }
            }
            else {
                this.sequence = 0;
            }

            this.lastTimestamp = timestamp;

            var id = ((timestamp - Twepoch) << 22) | (this.datacenterId << 17) | (this.workerId << 12) | this.sequence;
            return id;
        }

        private static long GetTimestamp() {
            return DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond - Twepoch;
        }

        private static long WaitNextMillis(long lastTimetamp) {
            var timestamp = GetTimestamp();
            while (timestamp <= lastTimetamp) {
                timestamp = GetTimestamp();
            }

            return timestamp;
        }

        #endregion

        #region GenerateUintId

        private static readonly int MachineId = Environment.MachineName.GetHashCode() & 0xFF; // 最多 256 个机器
        private static readonly int ProcessId = GetProcessId(); // 最多 256 个进程（服务端几乎够用了）

        private static int GetProcessId() {
#if UNITY_5_3_OR_NEWER || UNITY
            return System.Diagnostics.Process.GetCurrentProcess().Id & 0xFF;
#else
            return Environment.ProcessId & 0xFF;
#endif
        }

        private static readonly object _uintidlock = new();

        private static ushort _sequence = (ushort)new Random().Next(1, ushort.MaxValue);

        public static uint GenerateUintId() {
            lock (_uintidlock) {
                _sequence++;
                if (_sequence == 0)
                    _sequence = 1;

                uint conv = 0;

                conv |= (uint)(MachineId << 24);
                conv |= (uint)(ProcessId << 16);
                conv |= _sequence;

                return conv;
            }
        }

        #endregion

        #region Increment

        private static int _incrementId;

        public static int GenerateIncrementId() {
            var newId = Interlocked.Increment(ref _incrementId);

            if (newId == int.MaxValue) {
                Interlocked.CompareExchange(ref _incrementId, 0, int.MaxValue);
                newId = Interlocked.Increment(ref _incrementId);
            }

            return newId;
        }

        #endregion

        #region Simple

        public static int GenerateSimpleId() {
            return Guid.NewGuid().GetHashCode();
        }

        #endregion
    }
}